プログラムのソースコードなどの変更履歴を記録・追跡するための分散型バージョン管理システムだよ.
リーナス・トーバルズ
などなど…
などなど…
version(版)という概念のあるものすべてに応用可能
よくない.
Google driveでできないこと
恣意的な表
| 機能 | Google Drive | Git + Github |
|---|---|---|
| 変更履歴(revision)の記録 | ○ | ○ |
| 記録するタイミングの制御 | × | ○ |
| 記録するファイルの選択 | × | ○ |
| 復元 | ○ | ○ |
| 分岐 | × | ○ |
| 差分の表示 | × | ○ |
| 履歴の共有 | ○ | ○ |
Gitは分散型Version管理ツール
👉 local元に戻せればremoteのも元に戻せる!
2と3をひたすら繰り返す.
重犯罪人に自分の罪を思い知らせるなら、土を掘り移動させ、それからまた埋めて元に戻す、という作業を延々と繰り返させればよい。それは究極の拷問であり、最後には精神に異常をきたすであろう。(ドフトエフスキー?)
revesion管理基礎
分岐する場合を扱う
Cloudを利用
Gitには3つの視点がある
これらを理解すればGit master.
保存されているCommit (=Version)の親子関係を表すtree (一番重要).
1つのCommitの裏には3つのareaがある
Staging areaとgit directoryはユーザから直接見えない!
fileには4つの状態がある(three areasをfile側から見たもの)
あなたは,gitマスターになりました.
ここから先の作業はSource treeのHistory(履歴)画面でCommit treeを表示しながらやることをオススメします.
まずは,versionが分岐しない場合,ただの便利なbackup toolとしてgitを扱う方法を体験しましょう.
異世界に行ったら最初に唱える呪文.
File statusが4つのどの状態にあるかの確認.
これから何かする度になるべくgit statusしましょう.
current directoryにgit repositoryを作る
ls -a すると.git という隠しdirectoryが作られているはず.
これがgit repositoryであり,すべての変更履歴の情報はここに記録されていく(もっとも,直接見てわかるような形にはなっていない).
current directoryからgit repositoryを削除する
❗削除したrepositoryは元に戻せないので注意
fileをStaging areaに追加する.
git add [path ...]
git reset [--soft | --hard] [<commit>] [path ...]
stage(git add)されたすべてのファイルを対象にしたい場合
HEADは現在参照しているCommitを指すrefs(看板)
stage (git add)されたすべてのfileの現在の状態をGit Directoryへ記録する
git commit [--amend] [-m <msg>]
Git Directoryから最後のCommitを削除する
addもなかったことに
commitとaddの2つをまとめてなかったことに
復習: 1度作ったcommitは基本的になくならない(探せなくはなるので注意)
さっきなかったことにしたcommitは実は消えていない(みえなくなっただけ).
見えなくなったcommitを探すcommand = reflogを使う
既に何度か使っているgit reset これの役割は?
💡 偉い人に聞きましょう(54ページくらいまで読み進めてください)
| HEAD | インデックス | 作業ディレクトリ | 作業ディレクトリ保護の有無 | |
|---|---|---|---|---|
|
Commit Level |
||||
|
|
REF |
いいえ |
いいえ |
はい |
|
|
REF |
はい |
いいえ |
はい |
|
|
REF |
はい |
はい |
いいえ |
|
|
HEAD |
はい |
はい |
はい |
|
File Level |
||||
|
|
いいえ |
はい |
いいえ |
はい |
|
|
いいえ |
はい |
はい |
いいえ |
つまり?
git reset --hard, git checkout <file> (上表で太字のいいえになっているやつ) するときは気をつけよう.
fileをStaged areaとGit directoryから削除する
(=untracked(gitの追跡対象外)にする)
git rm [--cached] [path ...]
git rm --cached file000
git status # file000がgit repositoryから削除された
ls # Working directoryにはfile000が残っている実際のfileは削除せずにgitからfile000を削除(追跡対象外に)できた!
一生untrackedにしておきたいfileやdirectoryは,.gitignoreというfileを作ってそこに書く.
.gitignoreしたdirectory内の一部のfileのみをtrackedにしたい場合は,.gitkeepに書いてignoreされないようにする.
.gitignore(.gitkeep)については,自分でググってね. (i.e., あまり興味ない)
ファイルをWorking Directory, Staged area, Git directoryのすべてから削除する.
上の2つをまとめてやることもできる
ファイルをWorking Directory, Staged area, Git directoryのすべてにおいてmoveまたはrenameする.
Note: mvとrenameは本質的に同じ
git mv <source> <destination>
commitをなかったことにするのと同じ
最後のCommitを変更する
ファイルをaddし忘れたときとか,コメントを修正したい時とかに便利
復習: 1度作ったcommitは基本的になくならない(探せなくはなるので注意)
amendは,一見直前のCommitを改変したように見えるが,実は新しくCommitを作って元々のCommitを見えなくしただけ.
そのため,見えなくなったcommitを探すcommand = reflogでamend前のCommitを復元することができる.
HEADの指す位置をamend前のCommitに変える.
直前だけじゃなく,もっと前まで遡って自分の都合の良いように歴史を改変したい
git rebase -i <commit>
<commit>からHEADまでのコミットをまとめて変更する
# 準備
for f in file001 file002 file003 gomi; do cp file000 $f; done
# file001, file002, file003, gomiができれば何でも良い
git add file001
git commit -m 'Added file001.'
git add file002
git commit -m 'Added file002.'
git add file003
git commit -m '「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される'
git add gomi
git commit -m 'Added gomi.'git rebase -i <commit>
* bf1b31a (HEAD -> master) Added gomi. #
* fb7cc7b 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される # <-- HEAD~1 or HEAD^
* 469a642 Added file002. # <-- HEAD~2 or HEAD^^
* f8c8773 Added file001. # <-- HEAD~3 or HEAD^^^
* 2312317 Added file000. # <-- HEAD~4 or HEAD^^^^<commit>には変更の根本となるcommitを指定する.
この場合変更対象はHEAD - HEAD~3
pick 17b9da7 Added file001.
pick 98ee317 Added file002.
pick 9a7d9db 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
pick 00805cf Added gomi.
# Rebase e170b24..00805cf onto e170b24 (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out1番目と2番目はまとめたくて,3番目のメッセージを書き直したくて,4番目は消したい.
pick 17b9da7 Added file001.
s 98ee317 Added file002.
e 9a7d9db 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
d 00805cf Added gomi.
# Rebase e170b24..00805cf onto e170b24 (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out# This is a combination of 2 commits.
# This is the 1st commit message:
Added file001.
# This is the commit message #2:
Added file002.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sat Feb 16 22:50:41 2019 +0900
#
# interactive rebase in progress; onto e170b24
# Last commands done (2 commands done):
# pick 5b6d77f Added file001.
# squash 21c25a0 Added file002.
# Next commands to do (2 remaining commands):
# edit 2f734eb 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
# drop cf942f2 Added gomi.
# You are currently rebasing branch 'master' on 'e170b24'.
#
# Changes to be committed:
# new file: file001
# new file: file002# This is a combination of 2 commits.
# This is the 1st commit message:
Added file001 and file002.
# This is the commit message #2:
# Added file002.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sat Feb 16 22:50:41 2019 +0900
#
# interactive rebase in progress; onto e170b24
# Last commands done (2 commands done):
# pick 5b6d77f Added file001.
# squash 21c25a0 Added file002.
# Next commands to do (2 remaining commands):
# edit 2f734eb 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
# drop cf942f2 Added gomi.
# You are currently rebasing branch 'master' on 'e170b24'.
#
# Changes to be committed:
# new file: file001
# new file: file002[detached HEAD 0136ee2] Added file001 and file002.
Date: Sat Feb 16 22:50:41 2019 +0900
2 files changed, 2 insertions(+)
create mode 100644 file001
create mode 100644 file002
Stopped at 2f734eb... 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
You can amend the commit now, with
git commit --amend # <-- commitを編集したいのでまずは,こっち
Once you are satisfied with your changes, run
git rebase --continuegit logを見てみると<commit>で指定した根本から枝分かれしていることがわかる
「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sat Feb 16 22:50:41 2019 +0900
#
# interactive rebase in progress; onto e170b24
# Last commands done (3 commands done):
# squash 21c25a0 Added file002.
# edit 2f734eb 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
# Next command to do (1 remaining command):
# drop cf942f2 Added gomi.
# You are currently editing a commit while rebasing branch 'master' on 'e170b24'.
#
# Changes to be committed:
# new file: file003
#Added file003.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Sat Feb 16 22:50:41 2019 +0900
#
# interactive rebase in progress; onto e170b24
# Last commands done (3 commands done):
# squash 21c25a0 Added file002.
# edit 2f734eb 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
# Next command to do (1 remaining command):
# drop cf942f2 Added gomi.
# You are currently editing a commit while rebasing branch 'master' on 'e170b24'.
#
# Changes to be committed:
# new file: file003
#終わったらcontinue
rebaseの途中でやっぱりやめたくなった場合
rebaseを発動する前の状態に戻ることができる.
rebase対象が多すぎて途中でわけわからなくなったら,素直に–abortして最初からやり直そう.
rebaseしたあと,やっぱりなかったことにしたくなった
commit –amendと一緒で,rebaseは既存のcommitをコピーを作っているだけ
なので,元のcommitも残っている(見えなくなっているだけ).
見えないcommitをたどるにはreflog.
$ git reflog
a264b19 (HEAD -> master) HEAD@{0}: rebase -i (finish): returning to refs/heads/master
a264b19 (HEAD -> master) HEAD@{1}: commit (amend): Added file003.
ecc3571 HEAD@{2}: rebase -i (edit): 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
9070db1 HEAD@{3}: rebase -i (squash): Added file001 and file002.
408797f HEAD@{4}: rebase -i (start): checkout HEAD~4
92b7c29 HEAD@{5}: commit: Added gomi. # <-- rebase前のHEADの位置
...一度はrebaseをなかったことにしたんだけど,よくよく考えるとrebaseした後の方が良い気がしてきた…(よく考えてから行動しましょう!)
同じ理屈でなかったことにしたのをなかったことにできる
$ git reflog
92b7c29 (HEAD -> master) HEAD@{0}: reset: moving to HEAD@{5} # <-- さっきのreset
a264b19 (HEAD -> master) HEAD@{1}: rebase -i (finish): returning to refs/heads/master
a264b19 (HEAD -> master) HEAD@{2}: commit (amend): Added file003.
ecc3571 HEAD@{3}: rebase -i (edit): 「もしかして:再帰」をクリックすると「もしかして:再帰」が表示される
9070db1 HEAD@{4}: rebase -i (squash): Added file001 and file002.
408797f HEAD@{5}: rebase -i (start): checkout HEAD~4
92b7c29 HEAD@{6}: commit: Added gomi.
...なんでもかんでもなかったことにできるようになった!
もう何も怖くない.
次に,branchingによりversionが分岐する場合,作業環境の管理toolとしてgitを扱う方法を体験しましょう.
gitのbranchingは,プログラミング言語における**envのように,仮想環境を作って実行環境を分離するのと似た効果をもたらします.
基本の呪文 Commit treeを表示する
git log --oneline --graph --all
defaultのgit logは見にくいので,いい感じで表示してくれるwrapperもある.
$ git-foresta --all | less -RSX
99d7024c 2019-02-19 10:31 ○ ameono (master) Added file003
2f98c925 2019-02-19 10:31 ● ameono Added file001 and file002.
bf1b31ad 2019-02-19 10:31 │ ○ ameono (HEAD) Added gomi.
fb7cc7b0 2019-02-19 10:31 │ ● ameono 再帰で検索して「もしかして:再帰」をクリックすると…。
469a6426 2019-02-19 10:31 │ ● ameono Added file002.
f8c8773e 2019-02-19 10:31 │ ● ameono Added file001.
├─┘
2312317f 2019-02-19 10:30 ■ ameono Added file000.logの表示だけでなくCommitとかを便利にできるWrapperもある
保存されているCommit (=Version)の親子関係を表すtree (一番重要).
これからやる作業は基本的にrefsを作ったり移動したりする作業
git branch [-d|-D] <branch-name> [<start-point>]
feature/branch000という名前のrefを作る(指す先はmasterの指しているところ)
branchを削除する.
未mergeのbranchは削除しようとするエラーが出る(二度と見つけられなくなるため)ので強制削除する必要がある.
強制的に削除する場合.
HEADの指す先を
git checkout [-b] [<branch>|<commit>]
HEADの指す先をfeature/branch000へ変える
branchの作成とcheckoutをいっぺんにやる
gitを使ったことのある人は,checkoutに他の枝(branch)に移るというイメージを持っているかもしれないが,実際にはHEADの指す先のrefsが変わるだけ.
👉 e.g., masterもdevelopも同じCommitを指していたら,どちらにcheckoutしてもWorking directoryは変化しない. 同じCommitをmasterというrefs越しに見るかdevelopというrefs越しに見るかが違うだけ.
CLIで見てみる
$ git branch feature/update_file000
$ git log --graph --all
* commit e170b24890ca2685d9414b15f3862a28b16b3828 (HEAD -> master, feature/branch000)
Author: ameono <4m3on0@gmail.com>
Date: Sat Feb 16 21:28:33 2019 +0900
Added file000.$ git checkout feature/update_file000
$ git log --graph --all
* commit e170b24890ca2685d9414b15f3862a28b16b3828 (HEAD -> feature/branch000, master)
Author: ameono <4m3on0@gmail.com>
Date: Sat Feb 16 21:28:33 2019 +0900
Added file000.HEAD -> の指す先に注目.
<branch>にcheckoutした状態でCommitすると<branch>refsの位置はHEADに連動して移動する.
masterと違うCommitを指していることを確認
git checkout master
cat file000 # update_file000ブランチの変更が反映されていないことを確認
cp file000 file001
git add file001
git commit -m "Added file001."分岐したことを確認
このようにしてTaskごとに作業環境を切り分けることができる.
厳密には,checkoutのやっていることは,ただHEADの指す先を変えるのとは少し違う.
checkoutは特定のbranch(もちろんcommitでも良い)からfileをWorking directoryに取り出してくるcommandである.
と書くことで,特定の<branch>(or <commit>)の<path>にあるfileを現在のWorking directoryに持ってくることができる.
<path>を省略すると,その<branch>の全ファイルをWorking directoryに持ってくることになる.
全部持ってきたらWorking directoryの中身は,その<branch>と全く同じになる.
👉 HEADを移動したのと同じ.
このため,未commitのfileがあるとcheckoutは失敗する
(まだcommitされていないfileがcheckout先のfileで上書きされてしまう!).
他のbranchと今いるbranchを統合するcommitを作る
git merge [<branch>]
Githubを使うのであればこの作業はPRをMergeしたときに勝手にやってくれる.
GithubでPRをmergeするときはこんな感じのことをしている.
PRのMergeで問題が起きたときに対処できるようになるために,GithubがPRをMergeするときの動作を自分でやってみる.
$ git log --oneline --graph --all
* 0b45d32 (HEAD -> feature/update_file000) Merge branch 'master' into feature/update_file000
|\
| * 179a3bc (master) Added file001.
* | cf7cb13 Updated file000 (added the second row).
|/
* e170b24 Added file000.Merge commitができている.
$ git log --oneline --graph --all
* 500adbb (HEAD -> master) Merge branch 'feature/update_file000'
|\
| * 0b45d32 (feature/update_file000) Merge branch 'master' into feature/update_file000
| |\
| |/
|/|
* | 179a3bc Added file001.
| * cf7cb13 Updated file000 (added the second row).
|/
* e170b24 Added file000.Merge commitがまたできている.
$ git log --oneline --graph --all
* 500adbb (HEAD -> master) Merge branch 'feature/update_file000'
|\
| * 0b45d32 (feature/update_file000) Merge branch 'master' into feature/update_file000
| |\
| |/
|/|
* | 179a3bc Added file001. # <-- Merge前のmaterの位置
| * cf7cb13 Updated file000 (added the second row). # <-- Merge前のfeature/update_file000の位置
|/
* e170b24 Added file000.master tagとupdate_file000 tagをそれぞれMerge前の位置に戻せば良い.
(復習) HEAD^ = 1つ前の親Commit
Merge commitは親が2ついる. どうやって指定すれば?
親が2つ以上ある場合 HEAD^N (N=親の番号) のように指定できる.
$ git show --name-only HEAD^
commit 179a3bce92c28cb9a6ac1ef4d8ff4002c83d2f7d
Author: ameono <4m3on0@gmail.com>
Date: Sun Feb 17 00:57:33 2019 +0900
Added file001.
$ git show --name-only HEAD^2
commit 0b45d32e5332152a5a44400d6394344199f4f968 (feature/update_file000)
Merge: cf7cb13 179a3bc
Author: ameono <4m3on0@gmail.com>
Date: Sun Feb 17 14:51:11 2019 +0900
Merge branch 'master' into feature/update_file000HEAD~2と似てるけど,全然意味が違う.
HEAD~N = HEADのN個前の親Commit
HEAD^N = HEADの1個前の親CommitのN番目
ex) 1個前の親の2番めの親Commit = HEAD~1^2
masterのMergeをなかったことに
update_file000のMergeをなかったことに
$ git log --oneline --graph --all
* 179a3bc (master) Added file001.
| * cf7cb13 (HEAD -> feature/update_file000) Updated file000 (added the second row).
|/
* e170b24 Added file000.簡単ですね?
今までの話が理解できていれば,mergeをなかったことにしたのをなかったことにもできるはず.
(復習 GithubのPR Merge)
git checkout feature/update_file000
git merge master
git checkout master
git merge --no-ff feature/update_file000 <-- --no-ffに注目このoptionはなんぞや
git checkout feature/update_file000
git merge master
git checkout master
git merge feature/update_file000$ git log --oneline --graph --all
* e6de5fd (HEAD -> master, feature/update_file000) Merge branch 'master' into feature/update_file000
|\
| * 179a3bc Added file001.
* | cf7cb13 Updated file000 (added the second row).
|/
* e170b24 Added file000.2回MergeしたのにMerge commitが1つしかできていない.は?
💡 偉い人に聞きましょう(99ページくらいまで読み進めてください)
mergeと同じようなことをするcommandにrebaseがある.違いを見ましょう.
他のbranchの先端に今のbranchの根本から先端までのcommitをまとめて移植する
git rebase [<branch>]
git rebase --continue | --skip | --abort | --quit |
$ git checkout master
$ git rebase feature/update_file000
First, rewinding head to replay your work on top of it...
Fast-forwarded master to feature/update_file000.$ git log --oneline --graph --all
* a336ea3 (HEAD -> master, feature/update_file000) Added file001.
* cf7cb13 Updated file000 (added the second row).
* e170b24 Added file000.masterとfeatureは統合されたが,Merge commitが一個もできていない
merge: Merge Commitを作って2つのbranchを統合する
rebase: 2つのbranchのどちらかをもう片方の先端に移植することで統合する
まずはmasterを戻す.
次にupdate_file000を戻す
$ git reflog
a336ea3 (HEAD -> feature/update_file000) HEAD@{2}: checkout: moving from master to feature/update_file000
cf7cb13 (master) HEAD@{3}: reset: moving to HEAD^
a336ea3 (HEAD -> feature/update_file000) HEAD@{4}: rebase finished: returning to refs/heads/master
a336ea3 (HEAD -> feature/update_file000) HEAD@{5}: rebase: checkout feature/update_file000
cf7cb13 (master) HEAD@{6}: checkout: moving from feature/update_file000 to master
a336ea3 (HEAD -> feature/update_file000) HEAD@{7}: rebase finished: returning to refs/heads/feature/update_file000
a336ea3 (HEAD -> feature/update_file000) HEAD@{8}: rebase: Added file001.
cf7cb13 (master) HEAD@{9}: rebase: checkout master
179a3bc HEAD@{10}: checkout: moving from master to feature/update_file000 # <-- これ$ git reset --hard HEAD@{10}
$ git log --oneline --graph --all
* 179a3bc (HEAD -> feature/update_file000) Added file001.
| * cf7cb13 (master) Updated file000 (added the second row).
|/
* e170b24 Added file000.Mergeより戻すのが面倒(reflog使わないかん)ですね.
このことから,rebaseとmergeならmergeを使うべきという人もいます.
公式曰く,
公開リポジトリにプッシュしたコミットをリベースしてはいけない
この指針に従っている限り、すべてはうまく進みます。もしこれを守らなければ、あなたは嫌われ者となり、友人や家族からも軽蔑されることになるでしょう。(Pro Git)
💡 偉い人に聞きましょう(181ページくらいまで読み進めてください)
歴史改変のrebase(左)とbranchのrebase(右) やっていることは同じ.
他のbranchの特定のcommitを今のbranchの先端に移植する
git cherry-pick <commit>...
$ git log --oneline --graph --all
* 8a1eae4 (HEAD -> feature/update_file000) Updated file000 (added the second row).
* 179a3bc Added file001.
| * cf7cb13 (master) Updated file000 (added the second row).
|/
* e170b24 Added file000.masterのcommitがfeature/update_file000へcopyされた
rebaseはcherry-pickを複数まとめてやっているだけとも言える.
統合する複数のbranchで同じfileが変更されていると,CONFLICTが起こる
これは,Gitがどちらの歴史(Branch)が正しいか判断できないため.
userは自分で正しい歴史を選択する必要がある.
masterとfeatureの両方でfile000が編集されている状態を作り出す.
$ git merge master
Auto-merging file000
CONFLICT (content): Merge conflict in file000
Automatic merge failed; fix conflicts and then commit the result.🎉 CONFLCIT!!!
Conflictしたfileの中身
git checkout [--theirs|--ours] <paths> を使うそのまんま. Vimとかでひらいて↓こんな感じに編集して保存,AddしてCommit
merge元のbranch(=–theirs)の変更かmerge先(=HEAD, –ours)の変更かのどちらかを採用する.
利点: pdfや画像などテキストとして編集できないものがConflictしたときに便利
PDFをVimで開いて「この行はmerge先を採用して,こっちはbranchを採用…」とかやってるやつがいたら病気…
欠点: どういう結果になるか予想がつきにくい.
merge-toolには色々ある.
git mergetool --tool=vimdiff
DiffがあるChunk(=色が変わっている部分)にカーソルを合わせ, 1do, 2do, 3doと入力することで,それぞれ上段の左から1番目,2番目,3番目が今後の姿に取り込まれる.
PyCharmのようにConflictを解消するツールが付いているIDEもある.
Conflictを解消したら当該のfileをaddしてcommit.
❗Conflict解消のときは自動的にmessageが入力されるのでmオプションはいらない
成功すればMerge(or rebase, cherry-pickなど)が完了する.
GitHub は世界最大の Remote Git リポジトリホスティングサービスだよ.
つまり?
localのrepository = localのPCにおいてあるdirecotry
とすると,
Github上のrepository = Google driveにおいてあるdirecotry
のような関係.
有名どころ
Github
Gitlab
Bitbucket
Githubがやたら有名だが他にもある (Google driveとDropbox的な関係).
できることは大差ないので,お財布事情とUIの好みの問題
(あとは狸と猫のどっちが好きかとか…バケツ…).
とにかく有名 (連携サービスが多いイメージ).
お金たくさん持ってく.
Github Educationとか言う学生プランに加入するとGithub含め色々な有名サービスが無料で使える.
本田圭佑がいる
他の2つと違い自前のCIとかはない(他のツールと組み合わせる前提OSSの思想的には○?)
どちらかというと,自組織のサーバにインストールして自分たちしかアクセスできないRemote repositoryとして使う (要するに自前のfile server的なやつ)オンプレミス版のほうが有名.
Slackを水で薄めたようなchatアプリやCIがついてくるので,これひとつで開発に必要なすべての作業を完結させることができる.
大人の事情でPublic臭の強いGithubとかSlackとかを使えない人たちが使ってるっぽい(NASAとか).
一昔前までPrivate repository(外部公開されない) 作り放題な唯一のサービスだったので,個人の利用者が多くいた(今は,Githubも作り放題).
Source treeの開発元でもあるAtlassianの製品.
お金ないらしい(最近値上げした).
https://stackshare.io/stackups/bitbucket-vs-github-vs-gitlab
2019 2月現在,各サービスの無料プランの状況
| Github | Bitbucket | Github (学生) | Bitbucket (学生) | GitLab | |
|---|---|---|---|---|---|
| private repository | 無制限 | 無制限 | 無制限 | 無制限 | 無制限 |
| public repository | 無制限 | 無制限 | 無制限 | 無制限 | 無制限 |
| collaborator | 3名まで | 5名まで | 無制限 | 無制限 | 無制限 |
| CI build time | - | 50分/月 | - | 500分/月 | 2000分/月 |
| LFS | 1GB | 1GB | 1GB | 5GB | 無制限? |
| LFS Bandwidth | 1GB/月 | 無制限 | 1GB/月 | 無制限 | 無制限? |
最後に,remote repositoryと連携する方法を体験しましょう.
remoteとの連携はlocalのcommit treeをremoteに同期することと同義です.
最も単純なケースでは,localで更新されたtreeをそのままremoteにcopyして,remoteで更新されたtreeをlocalにcopyすることの繰り返しに近い動作になります.
Gitは分散型なので,remoteと連携する場合でもほとんどの作業はlocalで完結するようにできています.
localだけの作業と変わる部分は,<remote>/<branch>という特殊なrefsが追加されることだけです.
このrefsは<remote> repositoryにおいて<branch>が指しているcommitを指します.
remoteでこのbranchに新しいcommitが追加されれば,当然この<remote>/<branch>の指す先もその新しいcommitに更新されます.
この新しいcommitを取り込みたければ,<remote>/<branch>をlocalの同じ名前の<branch>にmergeするだけですみます.
つまり,remoteとはただのbranchの一つ(ただし,自分以外の誰かの手によって勝手に更新される)だと考えることができます.
この基本原則を忘れなければ,localの操作の延長線上でremoteの操作も可能です.
なお,特に設定なければ<remote>にはoriginという名前が使われます.
これは,master branchが勝手にmasterという名前になっているのと同じで,ただのデフォルト値であり,特別な意味はありません.
remote repositoryのcopyをlocalに作る.(= Download)
git clone <repository>
最初の一回しか使わない(あとはpullやfetch).
現在のremoteの<branch> refsの位置に合わせてlocalのorigin/<branch> refsの位置を更新する.
git fetch <remote> <branch>
このままではremoteのmaster(origin/master)がlocalのmasterに反映されていないので,masterをorigin/masterにmergeする.
remoteに合わせてlocalのrefsとcommitを更新するだけなので,git commitやgit branchをしたのと起こることは同じ.
よって, git commit OR git branchをなかったことにするときと同じ方法でなかったことにできる.
remote branchの変更をlocalのbranchに取り込む (Downloadに近い)
git pull [--rebase|--no-ff] <remote> <branch>
git fetch と git merge origin/master をまとめてやる(--rebaseつけるとgit rebase origin/masterになる)
Mergeの作業が入っているため,pullではConflictが発生する可能性がある.
ちなみに,GithubのPRという名前はGithubのPRのMergeにpull(fetch+merge)コマンドが含まれることに由来する
git mergeをなかったことにする方法 + git fetchをなかったことにする方法.
local branchの変更をremote branchに反映させる (Uploadに近い)
git push <remote> <branch>
git pullのremoteとlocalを逆にしたもの(pullと違いConflictする場合は実行できない= –ff-only).
何れかの原因でConflictする場合,pushはできないので適切な方法で対処する
git push -f <remote> <branch>, git push --force-with-lease <remote> <branch> で強制pushするgit push -f は強制的にremoteを上書きするので,ミスるとremoteからすべての歴史を吹き飛ばすので(頭真っ白になる), 吹き飛ばしても人に迷惑のかからないfeature branchのみで使うこと.
--force-with-lease オプションはローカルのrefsがremoteと一致している場合のみ(i.e., localが最新の場合)強制的にremoteを上書きするのでやや安全
git pull <remote> <branch> してConflictを解消してからpushする (pullはconflictが起こっても実行できるため)pushをなかったことにするには,さらにpushするしかない.
git push -f をして,push前の状態を強制上書きする.
git checkout <push前に<remote>/<branch>が指していたcommit>
git push -f <remote> HEAD:<branch> # localのHEADの位置にremoteの<branch>の位置を移動するもちろん,reset --hardで,localのcommitをなかったことにしてからpush -f してもよい.
あれ,さっきpush -f するなって言わなかったっけ?
そうです.なかったことにしなくて良いように,よく考えてからpushしましょう.
最新のmasterにfeatureをrebaseしたい場合の例
# pull version (HEAD -> feature)
git checkout master
git pull origin master
git checkout feature
git rebase master4 command -> 2 commmand
特に,HEADに未commitのfileがあるときにこれをやりたくなるとpullでは非常に面倒(stashを使えばいいのだが更に2 command増える)
branchをどのように切るかを決めるstyleの一つ.
別に守らなくてもErrorは起きないが,守ると便利なルール(Coding styleとかと同じ立ち位置).
featureとmasterしかないシンプルなFlow.
面倒なことはPR上でやる.
cloneするのにすごく時間がかかるようになります.
やっちゃったらGitの内側 - メインテナンスとデータリカバリを参考に当該のファイルを削除する(失敗するとCommitツリーを破壊するリスクがあるのでしないにこしたことはない)
Tips: Git LFS という大容量バイナリファイルを扱う方法も提供されている
featureでdevelopやmasterをrebaseするのはおk
git push -fするGithubでは,push -fできないようにbranchをprotectすることも可
やってしまっても大概なんとかなるけど,他の人にすごく迷惑がかかるので,喧嘩売りたいとき以外はやめましょう.
何かしようと思ったらとりあえずIssue起てる.
Added some files. #1 のようにすると1番のIssueのページから当該のCommitが見れるようになりトラッキングが楽.
よいPRには,LGTM (Looks Good To Me) を送りましょう.
なるべく少ないgit commandsでホールを回るのが目標.
模範解答のcommand数をPARxで表している(一部,バーディやイーグルも可).
(branch切るの忘れて作業してたので,あとから取り繕った体. masterの指す位置がclone直後と同じになっていればよい)
(masterではなくfeature branchにcheckoutした状態で終わる)
もっといい方法あったら教えてください.
特に以下の3つのコマンド(keyword)については一読しておいたほうが良い.